/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2019-2022. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * Description:
 * Author: huawei
 * Create: 2019-10-15
 */
#include <linux/mm.h>
#include <linux/mm_types.h>
#include <linux/spinlock_types.h>
#include <linux/rbtree.h>

#include "devmm_proc_info.h"
#include "devmm_common.h"
#include "devmm_page_cache.h"
#include "devmm_proc_mem_copy.h"
#include "svm_proc_mng.h"
#include "svm_proc_fs.h"
#include "svm_shmem_interprocess.h"
#include "svm_hot_reset.h"
#include "svm_task_dev_res_mng.h"
#include "svm_master_dma_desc_mng.h"
#include "svm_master_proc_mng.h"

#ifndef EMU_ST
#define SVM_ASYNC_TASK_MAX_ID 65535
#else
#define SVM_ASYNC_TASK_MAX_ID 10
#endif

static inline bool devmm_proc_async_task_is_empty(struct devmm_svm_process *svm_proc)
{
    struct devmm_svm_proc_master *master_data = (struct devmm_svm_proc_master *)svm_proc->priv_data;
    if (atomic_read(&(master_data->async_copy_record.task_cnt)) > 0) {
        return false;
    }
    return true;
}

static inline void devmm_proc_init_async_task_record(struct devmm_svm_proc_master *master_data)
{
    atomic_set(&(master_data->async_copy_record.task_cnt), 0);
    devmm_idr_init(&master_data->async_copy_record.task_idr, SVM_ASYNC_TASK_MAX_ID);
}

static inline void devmm_proc_destroy_async_task_record(struct devmm_svm_process *svm_proc)
{
    struct devmm_svm_proc_master *master_data = (struct devmm_svm_proc_master *)svm_proc->priv_data;

    devmm_idr_destroy(&master_data->async_copy_record.task_idr, devmm_kvfree);
}

static inline void devmm_proc_init_convert_dma_tree(struct devmm_svm_proc_master *master_data)
{
    u32 i;

    for (i = 0; i < DEVMM_CONVERT_TREE_NUM; i++) {
        master_data->convert_dma[i].dma_rbtree = RB_ROOT;
        spin_lock_init(&master_data->convert_dma[i].rbtree_lock);
    }
}

static void devmm_init_dma_desc_node_rb_info(struct devmm_dma_desc_node_rb_info *rb_info)
{
    rb_info->root = RB_ROOT;
    spin_lock_init(&rb_info->spinlock);
}

void devmm_free_proc_priv_data(struct devmm_svm_process *svm_proc)
{
    devmm_proc_destroy_async_task_record(svm_proc);
    devmm_kvfree(svm_proc->priv_data);
    svm_proc->priv_data = NULL;
}

int devmm_svm_proc_init_private(struct devmm_svm_process *svm_proc)
{
    struct devmm_svm_proc_master *master_data = devmm_kvzalloc(sizeof(struct devmm_svm_proc_master));
    if (master_data == NULL) {
        return -ENOMEM;
    }
    devmm_proc_init_async_task_record(master_data);
    devmm_proc_init_convert_dma_tree(master_data);
    devmm_init_task_dev_res_info(&master_data->task_dev_res_info);
    devmm_init_dma_desc_node_rb_info(&master_data->dma_desc_rb_info);
    svm_proc->priv_data = (void *)master_data;

    return 0;
}

bool devmm_svm_can_release_private(struct devmm_svm_process *svm_proc)
{
    if (devmm_proc_async_task_is_empty(svm_proc) == false) {
        return false;
    }
    if (devmm_notify_deviceprocess(svm_proc) != 0) {
        return false;
    }
    return true;
}

void devmm_svm_release_private_proc(struct devmm_svm_process *svm_proc)
{
    devmm_drv_info("Device process exited. (hostpid=%d; times=%d)\n", svm_proc->process_id.hostpid,
        svm_proc->release_work_cnt);

    devmm_dma_desc_nodes_destroy_by_task_release(svm_proc);
    devmm_task_dev_res_nodes_destroy_by_task(svm_proc);
    devmm_destroy_all_convert_dma_addr(svm_proc);
    devmm_destroy_ipc_mem_node_by_proc(svm_proc);
    devmm_destory_all_heap_by_proc(svm_proc);
    devmm_destroy_pages_cache(svm_proc);
    if (devmm_release_process_notice_pm(svm_proc) != 0) {
        devmm_drv_err("Vm notice pm failed. (hostpid=%d)\n", svm_proc->process_id.hostpid);
    }
    devmm_free_proc_priv_data(svm_proc);
    devmm_remove_pid_from_business(svm_proc->process_id.hostpid);
}

